Skip to content

fix: use exported name for namespaced DI token references#375

Open
brandonroberts wants to merge 1 commit into
mainfrom
fix/aliased-import-di-token
Open

fix: use exported name for namespaced DI token references#375
brandonroberts wants to merge 1 commit into
mainfrom
fix/aliased-import-di-token

Conversation

@brandonroberts

Copy link
Copy Markdown
Collaborator

Summary

Aliased imports used as DI tokens emitted a namespace member access keyed by the local binding instead of the module's exported name. For:

import { Foo as Bar } from "./m";   // m exports `Foo`
constructor(x: Bar) {}              // DI token

the compiler generated i1.Bar even though m only exports Foo, so the reference resolved to undefined at runtime — breaking Angular dependency injection (and bundlers flag it as a missing export, e.g. rolldown IMPORT_IS_UNDEFINED).

A namespace member access (i1.X) must always use the module's exported name, never the importer's local alias.

Root cause

Both code paths that build namespaced DI references used the local name:

  • component/transform.rsresolve_factory_dep_namespaces (factory/constructor deps) and resolve_host_directive_namespaces (host directives). These already had imported_name available on the import info but used the local name.
  • component/dependency.rscreate_token_expression (component constructor-dep path). Its R3DependencyMetadata only carried the local token name.

Fix

  • transform.rs: use import_info.imported_name, falling back to the local name when they match.
  • dependency.rs: add a token_imported_name field to the component R3DependencyMetadata, populated from the import map in component/decorator.rs, and use it when building the namespace property access.

Test plan

  • Added a regression test (component::dependency::tests::test_aliased_import_uses_exported_name) asserting the emitted reference uses the exported name (i1.ExportedName), not the local alias.
  • cargo test -p oxc_angular_compiler2356 passed, 0 failed.
  • cargo fmt -- --check clean.
  • Verified end-to-end on a real NgModule app (Bitwarden web vault, whose DI layer uses the import { X as XAbstraction } convention pervasively): IMPORT_IS_UNDEFINED warnings went 9 → 0 and the app now bootstraps with zero runtime errors (previously a hard Cannot read properties of undefined crash).

Notes

Found via that Bitwarden build. Separately, that app surfaced a distinct, pre-existing gap (not addressed here): non-standalone (@NgModule-declared) components are emitted with no directive dependencies/scope, so directives like NgIf don't match at runtime. That's a larger NgModule-compilation-scope feature and is out of scope for this fix.

🤖 Generated with Claude Code

Aliased imports used as DI tokens emitted a namespace member access keyed by the
local binding instead of the module's exported name. For
`import { Foo as Bar } from "./m"` used as a constructor dependency, the compiler
generated `i1.Bar` even though `m` only exports `Foo`, so the reference resolved to
`undefined` at runtime and broke injection (the bundler also flags it as a missing
export).

Both namespaced-DI-reference paths used the local name:
- factory deps and host directives in component/transform.rs
  (resolve_factory_dep_namespaces, resolve_host_directive_namespaces)
- the component constructor-dep path in component/dependency.rs
  (create_token_expression)

Carry the exported name through to both: transform.rs already had `imported_name`
on its import info; dependency.rs gains a `token_imported_name` field populated from
the import map in component/decorator.rs. A namespace member access now uses the
exported name, falling back to the local name when they match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brandonroberts

Copy link
Copy Markdown
Collaborator Author

@codex[agent] review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd0a02f393

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

d.token_source_module = Some(import_info.source_module.clone());
// Carry the exported name so namespaced refs (`i1.X`) use the export
// name, not the local alias, for aliased imports.
d.token_imported_name = import_info.imported_name.clone();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use exported names in ctor metadata too

When emit_class_metadata is enabled, which is the default, these same component constructor_deps are later passed to build_ctor_params_metadata; that path still uses the local token/type name for namespace property access (class_metadata/builders.rs:778 and :822). For import { Foo as Bar } ... constructor(x: Bar), the factory now emits i1.Foo, but ɵsetClassMetadata still emits ctorParameters: () => [{ type: i1.Bar }], so dev/TestBed metadata and bundlers still see the missing export for the exact aliased DI case this patch is addressing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex address the feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex[agent] address the feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant